package com.xzy.mybatis.cipher.support.registry;

import com.xzy.mybatis.cipher.support.metadata.CryptoColumnMetadata;
import com.xzy.mybatis.cipher.support.metadata.CryptoTableMetadata;
import lombok.Getter;

import java.lang.reflect.Field;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author xzy.xiao
 * @since 2025/1/14  19:47
 */
@Getter
public class CryptoMetadataRegistry {

    /**
     * {className:{fieldName:metadata}}
     */
    private final Map<String, Map<String, CryptoColumnMetadata>> class2fieldMetadataMap = new ConcurrentHashMap<>(16);

    /**
     * {datasourceName:{tableName:{columnName:metadata}}}
     */
    private final Map<String, Map<String, Map<String, CryptoColumnMetadata>>> datasource2table2columnMetadataMap = new ConcurrentHashMap<>(16);

    public void registerTableMetadata(CryptoTableMetadata tableMetadata) {
        synchronized (CryptoMetadataRegistry.class) {
            List<CryptoColumnMetadata> columnMetadataList = tableMetadata.getColumnMetadataList();
            if (columnMetadataList.isEmpty()) {
                return;
            }

            Map<String, CryptoColumnMetadata> fieldMetadataMap = new ConcurrentHashMap<>();
            Map<String, CryptoColumnMetadata> columnMetadataMap = new ConcurrentHashMap<>();
            for (CryptoColumnMetadata columnMetadata : columnMetadataList) {
                fieldMetadataMap.put(columnMetadata.getFieldName(), columnMetadata);
                columnMetadataMap.put(columnMetadata.getColumnName(), columnMetadata);
            }

            class2fieldMetadataMap.put(tableMetadata.getClassName(), fieldMetadataMap);

            datasource2table2columnMetadataMap
                    .computeIfAbsent(tableMetadata.getDatasourceName(), k -> new ConcurrentHashMap<>(16))
                    .put(tableMetadata.getTableName(), columnMetadataMap);
        }
    }

    public CryptoColumnMetadata getColumnMetadata(String datasourceName, String tableName, String columnName) {
        return Optional.ofNullable(datasource2table2columnMetadataMap.get(datasourceName))
                .map(table2columnMetadataMap -> table2columnMetadataMap.get(tableName))
                .map(columnMetadataMap -> columnMetadataMap.get(columnName))
                .orElse(null);
    }

    public CryptoColumnMetadata getColumnMetadata(Class<?> clazz, Field field) {
        Map<String, CryptoColumnMetadata> fieldMetadataMap = class2fieldMetadataMap.get(clazz.getName());
        return null == fieldMetadataMap ? null : fieldMetadataMap.get(field.getName());
    }

    public boolean isCryptoTable(String datasourceName, String tableName) {
        return Optional.ofNullable(datasource2table2columnMetadataMap.get(datasourceName))
                .map(table2columnMetadataMap -> table2columnMetadataMap.containsKey(tableName))
                .orElse(false);
    }

    public boolean isCryptoClass(String className) {
        return class2fieldMetadataMap.containsKey(className);
    }

    public boolean isCryptoColumn(String datasourceName, String tableName, String columnName) {
        CryptoColumnMetadata columnMetadata = getColumnMetadata(datasourceName, tableName, columnName);
        return columnMetadata != null;
    }

    public boolean isCryptoField(Class<?> clazz, Field field) {
        CryptoColumnMetadata columnMetadata = getColumnMetadata(clazz, field);
        return columnMetadata != null;
    }
}
